package top.v5it.japi.plus.core.spring.web.interceptor;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import top.v5it.japi.plus.core.spring.web.EadaAlgorithm;
import top.v5it.japi.plus.core.spring.web.JapiEada;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.function.Consumer;

import static top.v5it.japi.plus.core.ConstantPool.JAPIEADA_ALGORITHM_NAME;
import static top.v5it.japi.plus.core.ConstantPool.JAPIEADA_NAME;

/**
 * 脱敏加解密拦截，针对敏感数据字段进行脱敏拦截，
 * 拦截controller类中是否带有JsapiSafe注解并标识需要脱敏
 *
 * @author zhanpu
 * @date 20-12-23
 */
@Slf4j
public class JapiEadaInterceptor implements HandlerInterceptor {

    /**
     * 应用http请求或响应数据加解密算法(Encryption and decryption algorithm)
     * <pre>
     *     spring:
     *       japi:
     *         interceptor:
     *           eada:
     *             algorithm: RSA
     * </pre>
     *
     * @return {@link EadaAlgorithm}
     */
    @Setter
    private EadaAlgorithm algorithm;

    /**
     * 解析{@link JapiEada}并缓存
     *
     * @param request  {@link HttpServletRequest}
     * @param response {@link HttpServletResponse}
     * @param handler  {@link Object}
     * @return
     * @throws Exception
     */
    @Override
    @SuppressWarnings("unchecked")
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        clean(request);

        if (handler instanceof HandlerMethod) {
            // 查找 JapiEada 注解并标识请求需要加解密

            final HandlerMethod handlerMethod = (HandlerMethod) handler;
            final Class<?> clazz = handlerMethod.getBeanType();
            final Method method = handlerMethod.getMethod();

            // 调用toString()获取方法的唯一标识
            final String key = handlerMethod.toString() + "#japieada";
            final JapiEadaCache cache = JapiEadaCache.getInstance();

            Consumer<JapiEada> setting = v -> {
                request.setAttribute(JAPIEADA_NAME, v);
                request.setAttribute(JAPIEADA_ALGORITHM_NAME, algorithm);
                cache.putIfAbsent(key, v);
            };

            if (cache.containsKey(key)) {
                cache.get(key).ifPresent(setting);
                return true;
            }

            // 类或方法中是否加入JapiEada注解
            Class<JapiEada> japiEadaClass = JapiEada.class;

            JapiEada japiEada =
                    Optional.of(japiEadaClass)
                            .filter(method::isAnnotationPresent)
                            .map(method::getAnnotation)
                            .orElseGet(() ->
                                    Optional.of(japiEadaClass)
                                            .filter(clazz::isAnnotationPresent)
                                            .map(clazz::getAnnotation)
                                            .orElse(null));
            Optional.ofNullable(japiEada)
                    .ifPresent(setting);
        }

        return true;
    }

    /**
     * 清理可能被伪造数据
     *
     * @param request {@link HttpServletRequest}
     */
    private void clean(HttpServletRequest request) {
        // 防止被恶意伪造添加，先删除
        Optional.ofNullable(request.getAttribute(JAPIEADA_NAME))
                .ifPresent(v -> request.removeAttribute(JAPIEADA_NAME));

        Optional.ofNullable(request.getAttribute(JAPIEADA_ALGORITHM_NAME))
                .ifPresent(v -> request.removeAttribute(JAPIEADA_ALGORITHM_NAME));
    }

}
